package ru.niir.goodfil.audit.ui.beans;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import ru.niir.goodfil.audit.domain.*;
import ru.niir.goodfil.audit.ui.extractors.EventAttachmentExtractor;
import ru.niir.goodfil.audit.ui.extractors.EventDescriptionExtractor;
import ru.niir.goodfil.audit.ui.extractors.EventTypeExtractor;
import ru.niir.goodfil.audit.ui.extractors.EventValueExtractor;
import ru.niir.goodfil.audit.util.Assert;
import ru.niir.goodfil.excel.ExcelExport;
import ru.niir.goodfil.excel.SheetModelBuilder;
import ru.niir.goodfil.excel.client.SheetModel;
import ru.niir.goodfil.excel.client.WorkbookModel;
import ru.niir.goodfil.excel.impl.DateCellRenderer;
import ru.niir.goodfil.excel.impl.DefaultWorkbookModel;

import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.logging.Logger;

public class EventsExcelBean {
    private final static Logger logger = Logger.getLogger(EventsExcelBean.class.getName());
    private LogsDataTableBean logsDataTableBean;

    private static class LogMenuItem {
        private static int lastId = 0;

        private final int id;
        private final String title;
        private final Class klass;
        private final Criterion[] criterions;

        private LogMenuItem(String title, Class klass, Criterion... criterions) {
            lastId++;
            this.id = lastId;
            this.title = title;
            this.klass = klass;
            this.criterions = criterions;
        }

        public int getId() {
            return id;
        }

        public String getTitle() {
            return title;
        }

        public Class getKlass() {
            return klass;
        }

        public Criterion[] getCriterions() {
            return criterions;
        }
    }

    private static final List<LogMenuItem> logMenuItems = new ArrayList<LogMenuItem>();

    static {
        logMenuItems.add(new LogMenuItem("Безопасность", LoginEvent.class));
        logMenuItems.add(new LogMenuItem("Целостность окружения", EnviromentIntegrityEvent.class));
        logMenuItems.add(new LogMenuItem("Операции с заказами", EntityEvent.class, Restrictions.eq("klass", "ru.niir.goodfil.db.Order")));
        logMenuItems.add(new LogMenuItem("Операции с клиентами", EntityEvent.class, Restrictions.eq("klass", "ru.niir.goodfil.db.Client")));
        logMenuItems.add(new LogMenuItem("Операции с замещениями", EntityEvent.class, Restrictions.eq("klass", "ru.niir.goodfil.db.Replacement")));
        logMenuItems.add(new LogMenuItem("Операции с персональными ценами", EntityEvent.class, Restrictions.eq("klass", "ru.niir.goodfil.db.PersonalPrice")));
        logMenuItems.add(new LogMenuItem("Операции с товарами", EntityEvent.class, Restrictions.eq("klass", "ru.niir.goodfil.db.Good")));
        logMenuItems.add(new LogMenuItem("Персональные цены", PersonalPriceEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение настроек ИС", SettingModificationEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение пароля пользователя", PasswordChangeEvent.class));
        logMenuItems.add(new LogMenuItem("Ошибки ИС", ExceptionEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение кода 1С у клиента", ClientC1NumChangeEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение долга у клиента", ClientDebtChangeEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение информации о доставке у клиента", ClientDebtChangeEvent.class));
        logMenuItems.add(new LogMenuItem("Изменение ценовых колонок у клиента", ClientPriceColumnChangeEvent.class));
        logMenuItems.add(new LogMenuItem("Клиенты", ClientUIEvent.class));
        logMenuItems.add(new LogMenuItem("Товары", GoodUIEvent.class));
        logMenuItems.add(new LogMenuItem("Складские операции", OrderUIBuildEvent.class));
        logMenuItems.add(new LogMenuItem("Операции с заказами (UI)", OrderUIEvent.class));
        logMenuItems.add(new LogMenuItem("Операции с позициями заказов (UI)", OrderItemUIEvent.class));
        logMenuItems.add(new LogMenuItem("Складские операции", OrderUIBuildEvent.class));

        logMenuItems.add(new LogMenuItem("Пользователи", UserUIEvent.class));
    }

    private HibernateBean hibernateBean;

    private Date begdate = Calendar.getInstance().getTime();

    private Date enddate = Calendar.getInstance().getTime();

    private List selectedLogs;

    private List<Event> getEventsByCriteria(Class<? extends Event> klass, Criterion... criterions) {
        List<Event> events = new ArrayList<Event>();

        begdate.setHours(0);
        begdate.setMinutes(0);
        begdate.setSeconds(0);
        enddate.setHours(23);
        enddate.setMinutes(59);
        enddate.setSeconds(59);

        Session session = hibernateBean.getSession();
        Assert.notNull(session);

        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            Criteria criteria = session.createCriteria(klass);
            criteria.add(Restrictions.ge("date", begdate));
            criteria.add(Restrictions.le("date", enddate));

            if (criterions != null) {
                for (Criterion criterion : criterions) {
                    criteria.add(criterion);
                }
            }

            events = criteria.list();
            tx.commit();
        } finally {
            if (tx != null && !tx.wasCommitted()) {
                tx.rollback();
            }
        }


        return events;
    }

    /**
     * Открыть логи в файле MS Excel
     */
    public void openInExcelAction() throws IOException {
        List<Event> logs = loadLogs();
        exportToExcel(logs);
    }

    /**
     * Открыть логи в таблице в браузере
     */
    public void openInBrowserAction() {
        List<Event> logs = loadLogs();
        logsDataTableBean.init(logs);
    }

    private void redirect(String target) {
        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
        HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
        try {
            response.sendRedirect("AuditUI" + target);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Загрузка логов из БД
     *
     * @return
     */
    private List<Event> loadLogs() {
        final List<Event> result = new ArrayList<Event>();

        for (LogMenuItem logMenuItem : logMenuItems) {
            if (selectedLogs.contains(String.valueOf(logMenuItem.getId()))) {
                result.addAll(getEventsByCriteria(logMenuItem.getKlass(), logMenuItem.getCriterions()));
            }
        }

        Collections.sort(result, new Comparator<Event>() {
            @Override
            public int compare(Event o1, Event o2) {
                return o1.getDate().compareTo(o2.getDate());
            }
        });

        return result;
    }

    private void exportToExcel(List<Event> logs) throws IOException {
        Assert.notNull(logs);

        String tempCatalog = FacesContext.getCurrentInstance().getExternalContext().getInitParameter("ru.niir.audit.ui.TEMP_CATALOG");

        String uid = UUID.randomUUID().toString();
        String tempFile = tempCatalog + File.separator + uid + ".xls";

        logger.fine("Generating report");
        generateExcelFile(logs, tempFile);

        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();

        logger.fine("Publishing...");
        String link = retrieveFile(request, tempFile, uid + ".xls");
        logger.fine("Published to: " + link);

        HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
        response.sendRedirect(link);
    }

    public static String retrieveFile(HttpServletRequest request, String sourceFilePath, String targetFileName) {
        return retrieveFile(request.getRealPath("/"), sourceFilePath, targetFileName);
    }

    public static String retrieveFile(String realPath, String sourceFilePath, String targetFileName) {
        String link = "out/" + targetFileName;
        try {
            File f1 = new File(sourceFilePath);
            File f2 = new File(realPath + File.separator + "out" + File.separator + targetFileName);

            copyFile(f1, f2);

            logger.fine("Retrieving link `" + link + "` to file `" + f2.getPath() + "`. Source: `" + f1.getPath() + "`.");
            return link;
        } catch (Exception e) {
            e.printStackTrace();
            logger.warning("Cannot retrieve file: " + e);
            return null;
        }
    }

    private static void copyFile(java.io.File in, File out) throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(in);
            fos = new FileOutputStream(out);

            byte[] buf = new byte[1024];
            int i;
            while ((i = fis.read(buf)) != -1) fos.write(buf, 0, i);
        } finally {
            if (fis != null) fis.close();
            if (fos != null) fos.close();
        }
    }

    public static synchronized void generateExcelFile(List<Event> events, String target) throws IOException {
        Assert.notNull(events);
        Assert.notBlank(target);

        Set<Class> classes = new LinkedHashSet<Class>();
        for (Event event : events) {
            classes.add(event.getClass());
        }

        SheetModelBuilder sheetModelBuilder = new SheetModelBuilder("Протоколирование")
                .addIgnoredColumn("class")
                .addIgnoredColumn("id")
                .addColumn("date", "Дата")
                .setColumnRenderer(new DateCellRenderer("dd.MM.yyyy HH:mm:ss"))
                .setColumnWidth(20)
                .addColumn("log", "Журнал")
                .setColumnWidth(30)
                .setColumnValueExtractor(new EventTypeExtractor())
                .addColumn("ip", "IP")
                .setColumnWidth(15)
                .addColumn("login", "Пользователь")
                .setColumnWidth(20)
                .addColumn("event", "Событие")
                .setColumnWidth(40)
                .setColumnValueExtractor(new EventValueExtractor())
                .addColumn("details", "Подробности")
                .setColumnValueExtractor(new EventDescriptionExtractor())
                .setColumnWidth(255)
                .addColumn("attachments", "Приложения")
                .setColumnWidth(255)
                .setColumnValueExtractor(new EventAttachmentExtractor());


        WorkbookModel workbookModel = new DefaultWorkbookModel(new SheetModel[]{
                sheetModelBuilder.getSheetModel()
        });

        ExcelExport.generate(events, workbookModel, target);
    }

    public List<SelectItem> getLogs() {
        final List<SelectItem> result = new ArrayList<SelectItem>();
        for (LogMenuItem logMenuItem : logMenuItems) {
            result.add(new SelectItem(logMenuItem.getId(), logMenuItem.getTitle()));
        }
        return result;
    }

    public Date getBegdate() {
        return begdate;
    }

    public void setBegdate(Date begdate) {
        this.begdate = begdate;
    }

    public Date getEnddate() {
        return enddate;
    }

    public void setEnddate(Date enddate) {
        this.enddate = enddate;
    }

    public HibernateBean getHibernateBean() {
        return hibernateBean;
    }

    public void setHibernateBean(HibernateBean hibernateBean) {
        this.hibernateBean = hibernateBean;
    }

    public List getSelectedLogs() {
        return selectedLogs;
    }

    public void setSelectedLogs(List selectedLogs) {
        this.selectedLogs = selectedLogs;
    }

    public void setLogsDataTableBean(LogsDataTableBean logsDataTableBean) {
        this.logsDataTableBean = logsDataTableBean;
    }

    public LogsDataTableBean getLogsDataTableBean() {
        return logsDataTableBean;
    }
}
